package com.fastbird.ml;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.ByteArrayOutputStream;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import java.io.File;
import java.io.*;

/**
 * Created by yangguo on 2017/1/13.
 * 版本 1.4.4
 */
public class ScioModelImpl extends AbstractScioModel<PredictRequest,List<PredictAttributeResponse>>{

    private  String name;

    private  String version;

    private String id;

    private List<AttributeFeature> attributeFeatures;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public void setId(String id) {
        this.id = id;
    }

    public List<AttributeFeature> getAttributeFeatures() {
        return attributeFeatures;
    }

    public void setAttributeFeatures(List<AttributeFeature> attributeFeatures) {
        this.attributeFeatures = attributeFeatures;
    }

    public double[] getxMeanVector() {
        return xMeanVector;
    }

    public void setxMeanVector(double[] xMeanVector) {
        this.xMeanVector = xMeanVector;
    }

    private double[] xMeanVector;


    public  class AttributeFeature implements Serializable{
        private double[] feature;

        public double[] getxMean() {
            return xMean;
        }

        public void setxMean(double[] xMean) {
            this.xMean = xMean;
        }

        public double getyMean() {
            return yMean;
        }

        public void setyMean(double yMean) {
            this.yMean = yMean;
        }

        private double[] xMean;
        private double yMean;
        private String name;
        private String unit;
        private Double lower;
        private Double upper;

        public Double getLower() {
            return lower;
        }

        public void setLower(Double lower) {
            this.lower = lower;
        }

        public Double getUpper() {
            return upper;
        }

        public void setUpper(Double upper) {
            this.upper = upper;
        }


        public String getUnit() {
            return unit;
        }

        public void setUnit(String unit) {
            this.unit = unit;
        }


        public double[] getFeature() {
            return feature;
        }

        public void setFeature(double[] feature) {
            this.feature = feature;
        }

        public double getYmean() {
            return yMean;
        }

        public void setYmean(double ymean) {
            this.yMean = ymean;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    @Override
    public String getId() {
        return this.id;
    }

    /**
     * 默认构造器 模型参数采用默认值
     */
    public ScioModelImpl(){}

    /**
     * 从本地文件加载模型参数
     * @param modelFile
     * @throws IOException
     */
    public ScioModelImpl(File modelFile)throws IOException{
        if(!modelFile.exists()||!modelFile.isFile()){
            throw new FileNotFoundException("模型系数文件["+modelFile.getAbsolutePath()+"]不存在");
        }
        FileInputStream instream=new FileInputStream(modelFile);
        this.loadModelCoefficient(instream);
    }

    /**
     * 从流中加载模型文件
     * @param instream
     * @throws IOException
     */
    public ScioModelImpl(InputStream instream)throws IOException{
        if(instream==null) throw new IllegalArgumentException("传入的模型系数流不能为null!");
        if(instream!=null) {
            loadModelCoefficient(instream);
        }
    }

    /**
     * 从json字符串中加载模型文件
     * @param coefficientJsonStr
     */
    public ScioModelImpl(String coefficientJsonStr){
        loadModelCoefficient(coefficientJsonStr);
    }

    @Override
    public String getModelName() {
        return this.name;
    }

    @Override
    public String getModelVersion() {
        return this.version;
    }

    @Override
    public List<PredictAttributeResponse> predict(PredictRequest predictData){
        List<PredictAttributeResponse> result=new ArrayList<PredictAttributeResponse>();
        //(实测光谱-不点灯光谱)/(空腔光谱-不点灯光谱)
        double[] sampleVector= divide(
                sub(predictData.getSeries(),predictData.getNoLightSeries()),
                sub(predictData.getBaseLightSeries(),predictData.getNoLightSeries()));
        sampleVector=snvTransfer(sampleVector);
        for(AttributeFeature feature:this.attributeFeatures){
            Double predictValue=attributePredict(sampleVector,feature.getFeature(),feature.getYmean(),feature.xMean);
            PredictAttributeResponse resp=new PredictAttributeResponse(feature.getName(),predictValue,feature.lower,feature.upper,"");
            result.add(resp);
        }
        return result;
    }

    private void loadModelCoefficient(InputStream instream)throws IOException{
        byte[] temp=new byte[1024];
        ByteArrayOutputStream bufferStream=new ByteArrayOutputStream();
        int length=0;
        while((length=instream.read(temp))>=0){
            bufferStream.write(temp,0,length);
        }
        loadModelCoefficient(new String(bufferStream.toByteArray(),"utf-8"));
        unsafeCloseStream(instream);
        unsafeCloseStream(bufferStream);
    }

    private void loadModelCoefficient(String jsonStr) {
        System.out.println(jsonStr);
        Gson gson = new Gson();
        Type type = new TypeToken<Map<String, Object>>() {
        }.getType();
        Map<String, Object> coefficiens = gson.fromJson(jsonStr, type);
        Map<String, Object> feaures = (Map<String, Object>) coefficiens.get("features");
        Map<String, Double> yMeans = (Map<String, Double>) coefficiens.get("yMeans");

        Map<String, List<Double>> bounds = (Map<String, List<Double>>) coefficiens.get("bounds");
        Map<String,List<Double>> xMeans=(Map<String,List<Double>>)coefficiens.get("xMeans");


        this.name = (String) coefficiens.get("name");
        this.version = (String) coefficiens.get("version");
        this.id = (String) coefficiens.get("id");
        this.attributeFeatures = new ArrayList<AttributeFeature>();
        AttributeFeature tmpFeature;
        for (Map.Entry<String, Double> kv : yMeans.entrySet()) {
            tmpFeature = new AttributeFeature();
            tmpFeature.setName(kv.getKey());
            tmpFeature.setYmean(kv.getValue());
            List<Double> t = (List<Double>) feaures.get(kv.getKey());
            if (t == null)
                System.out.println(kv.getKey());
            else {
                List<Double> bound = bounds.get(kv.getKey());
                if (bound != null) {
                    tmpFeature.setFeature(toDoubles(t));
                    tmpFeature.setUpper(bound.get(0));
                    tmpFeature.setLower(bound.get(1));
                    tmpFeature.setxMean(toDoubles(xMeans.get(kv.getKey())));
                    this.attributeFeatures.add(tmpFeature);
                }
            }
        }
    }

    public static double[] toDoubles(List<Double> list){
        double[] res=new double[list.size()];
        for(int idx=0;idx<list.size();idx++){
            res[idx]=list.get(idx);
        }
        return res;
    }

    public static void unsafeCloseStream(Closeable stream){
        try{
            if(stream!=null)
                stream.close();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }

    private void logInfo(double[] vector,String tag){
        String res="";
        for(double i:vector){
            res+=i+",";
        }
    }
    private double attributePredict(double[] vector,double[] feacture,double yMean){
        double res;
        double[] sampleVector=sub(vector,xMeanVector);
        res=multi(sampleVector,feacture);
        res+=yMean;
        return res;
    }
    private double attributePredict(double[] vector,double[] feacture,double yMean,double[] xMean){
        double res;
        double[] sampleVector=sub(vector,xMean);
        res=multi(sampleVector,feacture);
        res+=yMean;
        return res;
    }

    @Deprecated()
    private double[] proprocessing(double[] sampleSeries,double[] noLightSeries,double[] baseLightSeries){
        double[] result=new double[sampleSeries.length];
        for(int idx=0;idx<sampleSeries.length;idx++){
            double r=1-(sampleSeries[idx]-noLightSeries[idx])/(baseLightSeries[idx]-noLightSeries[idx]);
            double k=Math.pow(1-r,2)/2*r;
            result[idx]=Math.log10(1+k-Math.sqrt(Math.pow(k,2)+2*k));
        }
        return result;
    }
    private double mean(double[] vector){
        double sum=0;
        for(double item:vector){
            sum+=item;
        }
        return sum/vector.length;
    }
    private double[] snvTransfer(double[] vector){
        double mean=mean(vector);
        double tmp=0;
        for(double item:vector){
            tmp+=Math.pow(item-mean,2);
        }
        tmp=Math.sqrt(tmp/(vector.length-1));
        return divide(sub(vector,mean),tmp);
    }
    private double[] sub(double[] vector,double t){
        double[] result=new double[vector.length];
        for(int index=0;index<result.length;index++){
            result[index]=vector[index]-t;
        }
        return  result;
    }
    private double[] sub(double[] vector1,double[] vector2 ){
        int length=Math.min(vector1.length,vector2.length);
        double[] result=new double[length];
        for(int index=0;index<length;index++){
            result[index]=vector1[index]-vector2[index];
        }
        return  result;
    }

    private double[] divide(double[] vector,double t){
        double[] result=new double[vector.length];
        for(int index=0;index<vector.length;index++){
            result[index]=vector[index]/t;
        }
        return result;
    }
    private double[] divide(double[] vector1,double[] vector2){
        int length=Math.min(vector1.length,vector2.length);
        double[] result=new double[length];
        for(int index=0;index<length;index++){
            result[index]=vector1[index]/vector2[index];
        }
        return result;
    }
    private double multi(double[] vector1,double[] vector2){
        double result=0;
        int length=Math.min(vector1.length,vector2.length);
        for(int index=0;index<length;index++){
            result+=vector1[index]*vector2[index];
        }
        return result;
    }


    @Override
    public String toString() {
        Gson gson=new Gson();
        return gson.toJson(this);
    }
}
